package com.hk.core.service.reactive.impl;

import com.hk.commons.query.Order;
import com.hk.commons.query.QueryModel;
import com.hk.commons.query.SortUtils;
import com.hk.commons.query.page.QueryPage;
import com.hk.commons.util.AssertUtils;
import com.hk.commons.util.ObjectUtils;
import com.hk.core.data.r2dbc.repository.BaseR2dbcRepository;
import com.hk.core.jdbc.query.Condition;
import com.hk.core.jdbc.query.ConditionQueryModel;
import com.hk.core.service.reactive.R2dbcReactiveBaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Persistable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.Serializable;
import java.util.List;
import java.util.Objects;
import java.util.function.UnaryOperator;

/**
 * @author Kevin
 */
public abstract class R2dbcReactiveBaseServiceImpl<T extends Persistable<ID>, ID extends Serializable>
        implements R2dbcReactiveBaseService<T, ID> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private Class<T> domainClass;

    protected abstract BaseR2dbcRepository<T, ID> getBaseRepository();

    protected ExampleMatcher ofExampleMatcher() {
        return ExampleMatcher.matchingAll().withIgnoreNullValues();
    }

    /**
     * Example 查询参数不能为null
     *
     * @param t t
     * @return t
     */
    private T checkNull(T t) {
        return ObjectUtils.defaultIfNull(t, BeanUtils.instantiateClass(getDomainClass()));
    }

    @SuppressWarnings("unchecked")
    private Class<T> getDomainClass() {
        if (Objects.isNull(domainClass)) {
            ResolvableType resolvableType = ResolvableType.forClass(getClass());
            this.domainClass = (Class<T>) resolvableType.getSuperType().getGeneric(0).resolve();
        }
        return domainClass;
    }

    @Override
    public Mono<T> findOne(T t) {
        return getBaseRepository().findOne(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public Mono<QueryPage<T>> queryForPage(ConditionQueryModel queryModel) {
        return getBaseRepository().queryForPage(queryModel);
    }

    @Override
    public Flux<T> findAll(T t, Order... orders) {
        return getBaseRepository().findAll(Example.of(checkNull(t), ofExampleMatcher()), orders);
    }

    @Override
    public Flux<T> findAll(List<Condition> conditions, Order... orders) {
        return getBaseRepository().findAll(conditions, orders);
    }

    @Override
    public Mono<QueryPage<T>> queryForPage(QueryModel<T> query) {
        return getBaseRepository().queryForPage(Example.of(query.getParam(), ofExampleMatcher()),
                PageRequest.of(query.getStartRowIndex(), query.getPageSize(), SortUtils.toSort(query.getOrders())));
    }

    @Override
    public Mono<Boolean> exists(T t) {
        return getBaseRepository().exists(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public Mono<Long> count(T t) {
        return getBaseRepository().count(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public Mono<Void> deleteById(ID id) {
        return getBaseRepository().deleteById(id);
    }

    @Override
    public Mono<Void> deleteAllById(Iterable<ID> ids) {
        return getBaseRepository().deleteAllById(ids);
    }

    @Override
    public Mono<Void> delete(T entity) {
        return getBaseRepository().delete(entity);
    }

    @Override
    public Mono<Void> delete(Iterable<T> entities) {
        return getBaseRepository().deleteAll(entities);
    }

    @Override
    public Mono<T> insert(T t, UnaryOperator<T> operator) {
        return getBaseRepository().save(operator.apply(t));
    }

    @Override
    public Flux<T> batchInsert(Iterable<T> entities) {
        return getBaseRepository().saveAll(entities);
    }

    @Override
    public Mono<T> updateById(T t, UnaryOperator<T> operator) {
        var entity = operator.apply(t);
        AssertUtils.isTrue(!entity.isNew(), "更新实体ID不能为空");
        return getBaseRepository().save(entity);
    }

    @Override
    public Mono<T> findById(ID id) {
        return getBaseRepository().findById(id);
    }

    @Override
    public Flux<T> findAll(Order... orders) {
        return getBaseRepository().findAll(SortUtils.toSort(orders));
    }

    @Override
    public Flux<T> findByIds(Iterable<ID> ids) {
        return getBaseRepository().findAllById(ids);
    }

    @Override
    public Mono<Boolean> existsById(ID id) {
        return getBaseRepository().existsById(id);
    }

    @Override
    public Mono<Long> count() {
        return getBaseRepository().count();
    }
}
